﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Threading;
using System.Diagnostics;

using System.Net.Sockets;
using System.Text;
using System.IO;
using Microsoft.Phone;

using System.Net.NetworkInformation;

using sdkMulticastCS;

namespace MulticastCamera
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Camera
        PhotoCamera _camera = null;
        Thread _thread;
        bool _threadPleaseExit;

        private WriteableBitmap writableBitmap = null;

        // Multicast
        // http://msdn.microsoft.com/en-us/library/hh286407%28v=vs.92%29.aspx

        // The address of the multicast group to join.
        // Must be in the range from 224.0.0.0 to 239.255.255.255
        private const string GROUP_ADDRESS = "224.0.1.11";

        // The port over which to communicate to the multicast group
        private const int GROUP_PORT = 54329;
        
        // true if we have joined the multicast group; otherwise, false
        bool _joined = false;

        // Maximum size of a message in this communication
        private const int MAX_MESSAGE_SIZE = 1024;

        private const int MAX_SIZE_H = 123;
        private const int MAX_SIZE_W = 164;

        private const int OWN_BYTES_COUNT = 3;

        private bool receiving = true;
        private MemoryStream readingMemoryStream = null;
        private int counter = 0;

        private MemoryStream jpegMemoryStream = null;

        private UdpAnySourceMulticastChannel Channel { get; set; }

        private int receivedPictureIndex = 0;
        private int receivedMaxBlockCount = 0;

        public MainPage()
        {
            InitializeComponent();

            writableBitmap = new WriteableBitmap(MAX_SIZE_W, MAX_SIZE_H);
            this.image.Source = writableBitmap;

            this.Channel = new UdpAnySourceMulticastChannel(GROUP_ADDRESS, GROUP_PORT);
            this.Channel.Joined += new EventHandler(Channel_Joined); 
            this.Channel.PacketReceived += new EventHandler<UdpPacketReceivedEventArgs>(Channel_PacketReceived);
            this.Channel.Open();

            this.sendbtn.IsEnabled = true;
            this.reveicebtn.IsEnabled = false;
        }

        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            StopCamera();
            this.Channel.Close();
        }
        
        private void SendBytes(byte[] data)
        {
            this.Channel.SendBytes(data);
        }

        public void StartCamera()
        {
            if (_camera == null)
            {
                _camera = new PhotoCamera(CameraType.Primary);
                _camera.Initialized += new EventHandler<CameraOperationCompletedEventArgs>(CameraInitialized);
                viewfinderBrush.SetSource(_camera);
            }
        }

        public void StopCamera()
        {
            if (_camera != null)
            {
                // Notify the background worker to stop processing.
                _threadPleaseExit = true;
                _thread.Join();

                // Dispose camera to minimize power consumption and to expedite shutdown.
                _camera.Dispose();
                _camera = null;
            }
        }

        private void CameraInitialized(object sender, CameraOperationCompletedEventArgs e)
        {
            if (_camera != null)
            {
                _camera.FlashMode = FlashMode.Off;

                Dispatcher.BeginInvoke(delegate()
                {
                    // Set the orientation of the viewfinder.
                    viewfinderBrushTransformation.Angle = _camera.Orientation;
                    this.image.Source = writableBitmap;
                });

                // Start the background worker thread that processes the camera preview buffer frames.
                _threadPleaseExit = false;
                _thread = new Thread(ReadFramesBackgroundWorker);
                _thread.Start();
            }
        }

        BitmapImage GetImage(byte[] rawImageBytes)
        {
            BitmapImage imageSource = null;
            using (MemoryStream stream = new MemoryStream(rawImageBytes, 0, rawImageBytes.Length))
            {
                stream.Position = 0;
                imageSource = new BitmapImage();
                imageSource.CreateOptions = BitmapCreateOptions.None;
                imageSource.SetSource(stream);
            }
            return imageSource;
        }

        public byte[] ToByteArray(WriteableBitmap bmp)
        {
            int[] p = bmp.Pixels;
            int len = p.Length * 4;
            byte[] result = new byte[len]; // ARGB
            Buffer.BlockCopy(p, 0, result, 0, len);
            return result;
        }

        public byte[] ToByteArrayFromIntArray(int[] p)
        {
            int len = p.Length * 4;
            byte[] result = new byte[len]; // ARGB
            Buffer.BlockCopy(p, 0, result, 0, len);
            return result;
        }

        public int[] ToIntArrayFromByteArray(byte[] barray)
        {
            int len = barray.Length /4;
            int[] result = new int[len]; // ARGB
            Buffer.BlockCopy(barray, 0, result, 0, len);
            return result;
        }

        public void FromByteArray(WriteableBitmap bmp, byte[] buffer)
        {
            Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
        }

        private int[] scaleDown(int[] sourceImage, int sourceWidth, int sourceHeight, int targetWidth, int targetHeight)
        {
            int[] rval = new int[targetWidth * targetHeight];
            int sx_inc = sourceWidth * 1024 / targetWidth;
            int sy_inc = sourceHeight * 1024 / targetHeight;
            int sy = sy_inc / 2;

            for (int y = 0; y < targetHeight; y++)
            {
                int sx = sx_inc / 2;
                for (int x = 0; x < targetWidth; x++)
                {
                    rval[y * targetWidth + x] = sourceImage[sourceWidth * (sy >> 10) + (sx >> 10)];
                    sx += sx_inc;
                }
                sy += sy_inc;
            }
            return rval;
        }


        private void ReadFramesBackgroundWorker()
        {

            var bufferLayout = _camera.YCbCrPixelLayout;
            int[] currentFrame = new int[(int)_camera.PreviewResolution.Width * (int)_camera.PreviewResolution.Height];

            while (!_threadPleaseExit)
            {
                // Get the current preview buffer from the camera
                _camera.GetPreviewBufferArgb32(currentFrame);

                // Scale down image size
                int[] scaledDownImage = scaleDown(currentFrame,
                    (int)_camera.PreviewResolution.Width,
                    (int)_camera.PreviewResolution.Height,
                    MAX_SIZE_W, MAX_SIZE_H);

                // image to JPEG
                byte[] bytesBig = ToByteArrayFromIntArray(scaledDownImage);
                jpegMemoryStream = new MemoryStream();
                FromByteArray(writableBitmap, bytesBig);
                writableBitmap.SaveJpeg(jpegMemoryStream, writableBitmap.PixelWidth, writableBitmap.PixelHeight, 0, 30);
                byte[] bytes = new byte[(int)jpegMemoryStream.Length];
                Buffer.BlockCopy(jpegMemoryStream.GetBuffer(), 0, bytes, 0, (int)jpegMemoryStream.Length);
                jpegMemoryStream.Close();
                System.Diagnostics.Debug.WriteLine(bytes.Length);

                counter++;

                Deployment.Current.Dispatcher.BeginInvoke(delegate()
                {
                this.PageTitle.Text = "Sending... ";
                writableBitmap.Invalidate();
                });

                System.Diagnostics.Debug.WriteLine("Sending picture nro " + counter);
                
                // Send JPEG camera frames to UDP
                if (_joined && !receiving)
                {
                    int pos = 0;
                    int blokIndex = 1;
                    int blokMaxCount = (bytes.Length / MAX_MESSAGE_SIZE) + 1;
                    byte[] sendBytes = new byte[MAX_MESSAGE_SIZE];

                    while (pos + MAX_MESSAGE_SIZE < bytes.Length)
                    {
                        Buffer.BlockCopy(bytes, pos, sendBytes, 0, MAX_MESSAGE_SIZE - OWN_BYTES_COUNT);
                        sendBytes[sendBytes.Length - 1] = (byte)blokIndex;
                        sendBytes[sendBytes.Length - 2] = (byte)counter;
                        sendBytes[sendBytes.Length - 3] = (byte)blokMaxCount;
                        System.Diagnostics.Debug.WriteLine("Send: block " + blokIndex + "/"+blokMaxCount+ ", pictureIndex " + counter);
                        SendBytes(sendBytes);
                        pos += MAX_MESSAGE_SIZE - OWN_BYTES_COUNT - 1;
                        blokIndex++;
                        Thread.Sleep(200);
                    }
                    if (pos + MAX_MESSAGE_SIZE > bytes.Length)
                    {
                        Buffer.BlockCopy(bytes, pos, sendBytes, 0, bytes.Length - pos);
                        sendBytes[sendBytes.Length - 1] = (byte)blokIndex;
                        sendBytes[sendBytes.Length - 2] = (byte)counter;
                        sendBytes[sendBytes.Length - 3] = (byte)blokMaxCount;
                        System.Diagnostics.Debug.WriteLine("Send: block " + blokIndex + "/" + blokMaxCount + ", pictureIndex " + counter);
                        SendBytes(sendBytes);
                        pos += MAX_MESSAGE_SIZE - OWN_BYTES_COUNT - 1;
                        blokIndex++;
                    }
                }
                Thread.Sleep(1000);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Sending
            receiving = false;
            this.PageTitle.Text = "Sending...";
            this.sendbtn.IsEnabled = false;
            this.reveicebtn.IsEnabled = true;
            counter = 0;

            StartCamera();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // Receiving
            receiving = true;
            this.PageTitle.Text = "Receiving...";
            this.sendbtn.IsEnabled = true;
            this.reveicebtn.IsEnabled = false;
            counter = 0;

            StopCamera();
        }

        void Channel_PacketReceived(object sender, UdpPacketReceivedEventArgs e)
        {
            ProcessReceivedBytes(e.bytes);
        }

        void CleanMemoryStream(ref MemoryStream stream)
        {
            byte[] b = new byte[stream.Capacity];
            stream.Position = 0;
            stream.Write(b, 0, b.Length);
            stream.Flush();
        }

        void ProcessReceivedBytes(byte[] bytes)
        {
            Deployment.Current.Dispatcher.BeginInvoke(delegate()
            {
                this.PageTitle.Text = "Receiving... ";
                bool firstBytes = false;
                if (readingMemoryStream == null)
                {
                    // First bytes received. Initialize receiving
                    firstBytes = true;
                    readingMemoryStream = new MemoryStream();
                    this.receivedMaxBlockCount = (int)bytes[bytes.Length - 3];
                    this.receivedPictureIndex = (int)bytes[bytes.Length - 2];
                    CleanMemoryStream(ref readingMemoryStream);
                }

                int blokIndex = (int)bytes[bytes.Length - 1];
                int pictureIndex = (int)bytes[bytes.Length - 2];
                int blokMaxCount = (int)bytes[bytes.Length - 3];
                System.Diagnostics.Debug.WriteLine("Received: block " + blokIndex + "/" + blokMaxCount + ", pictureIndex " + pictureIndex);

                // New picture received
                // Show previous if we have it whole
                if (pictureIndex != this.receivedPictureIndex)
                {
                    if (this.receivedMaxBlockCount <= 0 && !firstBytes)
                        ShowReceivedPicture();

                    this.receivedMaxBlockCount = blokMaxCount;
                }
                
                // Store received picture binary
                this.receivedPictureIndex = pictureIndex;
                this.receivedMaxBlockCount--;
                if (blokIndex == 1)
                {
                    readingMemoryStream.Position = 0;
                }
                else
                {
                    readingMemoryStream.Position = ((blokIndex - 1) * (MAX_MESSAGE_SIZE - OWN_BYTES_COUNT - 1));
                }
                readingMemoryStream.Write(bytes, 0, bytes.Length - OWN_BYTES_COUNT);
            });
        }


        private void ShowReceivedPicture()
        {
            try
            {
                // Decode JPEG
                readingMemoryStream.Position = 0;
                writableBitmap = PictureDecoder.DecodeJpeg(readingMemoryStream);
                this.image.Source = writableBitmap;
                writableBitmap.Invalidate();
                CleanMemoryStream(ref readingMemoryStream);
            }
            catch
            {
                System.Diagnostics.Debug.WriteLine("ERROR");
            }
        }


        void Channel_Joined(object sender, EventArgs e)
        {
            this._joined = true;
            System.Diagnostics.Debug.WriteLine("joined");
        }


    }

}